﻿using FantasyWereld.Code;
using Guild_Wars_2.Common;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Threading.Tasks;
using Windows.ApplicationModel;
using Windows.ApplicationModel.Resources.Core;
using Windows.Data.Xml.Dom;
using Windows.Foundation;
using Windows.Foundation.Collections;
using Windows.Storage;
using Windows.UI.Xaml.Data;
using Windows.UI.Xaml.Media;
using Windows.UI.Xaml.Media.Imaging;

// The data model defined by this file serves as a representative example of a strongly-typed
// model that supports notification when members are added, removed, or modified.  The property
// names chosen coincide with data bindings in the standard item templates.
//
// Applications may use this model as a starting point and build on it, or discard it entirely and
// replace it with something appropriate to their needs.

namespace Guild_Wars_2.Data
{
    /// <summary>
    /// Base class for <see cref="GW2ServerItem"/> and <see cref="GW2ServerGroup"/> that
    /// defines properties common to both.
    /// </summary>
    [Windows.Foundation.Metadata.WebHostHidden]
    public abstract class GW2DataCommon : Guild_Wars_2.Common.BindableBase
    {
        private static Uri _baseUri = new Uri("ms-appx:///");

        public GW2DataCommon(String uniqueId, String title, String imagePath)
        {
            this._uniqueId = uniqueId;
            this._title = title;
            this._imagePath = imagePath;
        }

        private string _uniqueId = string.Empty;
        public string UniqueId
        {
            get { return this._uniqueId; }
            set { this.SetProperty(ref this._uniqueId, value); }
        }

        private string _title = string.Empty;
        public string Title
        {
            get { return this._title; }
            set { this.SetProperty(ref this._title, value); }
        }

        private ImageSource _image = null;
        private String _imagePath = null;
        public ImageSource Image
        {
            get
            {
                if (this._image == null && this._imagePath != null)
                {
                    this._image = new BitmapImage(new Uri(GW2DataCommon._baseUri, this._imagePath));
                }
                return this._image;
            }

            set
            {
                this._imagePath = null;
                this.SetProperty(ref this._image, value);
            }
        }

        public void SetImage(String path)
        {
            this._image = null;
            this._imagePath = path;
            this.OnPropertyChanged("Image");
        }

        public override string ToString()
        {
            return this.Title;
        }
    }

    /// <summary>
    /// Generic item data model.
    /// </summary>
    public class GW2ServerItem : GW2DataCommon
    {
        public GW2ServerItem()
            : base(String.Empty, String.Empty, String.Empty)
        {
        }
        
        public GW2ServerItem(String uniqueId, String title, String imagePath, String content, GW2ServerGroup group)
            : base(uniqueId, title, imagePath)
        {
            this._group = group;
        }

        private string _content = string.Empty;
        public string Content
        {
            get { return this._content; }
            set { this.SetProperty(ref this._content, value); }
        }

        private GW2ServerGroup _group;
        public GW2ServerGroup Group
        {
            get { return this._group; }
            set { this.SetProperty(ref this._group, value); }
        }
    }

    /// <summary>
    /// Generic group data model.
    /// </summary>
    public class GW2ServerGroup : GW2DataCommon
    {
        public GW2ServerGroup(String uniqueId, String title, String imagePath)
            : base(uniqueId, title, imagePath)
        {
            Items.CollectionChanged += ItemsCollectionChanged;
        }

        private void ItemsCollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
        {
            // Provides a subset of the full items collection to bind to from a GroupedItemsPage
            // for two reasons: GridView will not virtualize large items collections, and it
            // improves the user experience when browsing through groups with large numbers of
            // items.
            //
            // A maximum of 48 items are displayed because it results in filled grid columns
            // whether there are 1, 2, 3, 4, ... or 24 rows displayed

            switch (e.Action)
            {
                case NotifyCollectionChangedAction.Add:
                    if (e.NewStartingIndex < 48)
                    {
                        TopItems.Insert(e.NewStartingIndex,Items[e.NewStartingIndex]);
                        if (TopItems.Count > 48)
                        {
                            TopItems.RemoveAt(48);
                        }
                    }
                    break;
                case NotifyCollectionChangedAction.Move:
                    if (e.OldStartingIndex < 48 && e.NewStartingIndex < 48)
                    {
                        TopItems.Move(e.OldStartingIndex, e.NewStartingIndex);
                    }
                    else if (e.OldStartingIndex < 48)
                    {
                        TopItems.RemoveAt(e.OldStartingIndex);
                        TopItems.Add(Items[11]);
                    }
                    else if (e.NewStartingIndex < 48)
                    {
                        TopItems.Insert(e.NewStartingIndex, Items[e.NewStartingIndex]);
                        TopItems.RemoveAt(48);
                    }
                    break;
                case NotifyCollectionChangedAction.Remove:
                    if (e.OldStartingIndex < 48)
                    {
                        TopItems.RemoveAt(e.OldStartingIndex);
                        if (Items.Count >= 48)
                        {
                            TopItems.Add(Items[47]);
                        }
                    }
                    break;
                case NotifyCollectionChangedAction.Replace:
                    if (e.OldStartingIndex < 48)
                    {
                        TopItems[e.OldStartingIndex] = Items[e.OldStartingIndex];
                    }
                    break;
                case NotifyCollectionChangedAction.Reset:
                    TopItems.Clear();
                    while (TopItems.Count < Items.Count && TopItems.Count < 48)
                    {
                        TopItems.Add(Items[TopItems.Count]);
                    }
                    break;
            }
        }

        private ObservableCollection<GW2ServerItem> _items = new ObservableCollection<GW2ServerItem>();
        public ObservableCollection<GW2ServerItem> Items
        {
            get { return this._items; }
        }

        private ObservableCollection<GW2ServerItem> _topItem = new ObservableCollection<GW2ServerItem>();
        public ObservableCollection<GW2ServerItem> TopItems
        {
            get {return this._topItem; }
        }
    }

    /// <summary>
    /// Creates a collection of groups and items with hard-coded content.
    /// 
    /// GW2DataSource initializes with placeholder data rather than live production
    /// data so that sample data is provided at both design-time and run-time.
    /// </summary>
    public sealed class GW2DataSource
    {
        private static XmlDocument _GW2Worlds = new XmlDocument();
        private static XmlDocument _GW2Events = new XmlDocument();
        private static XmlDocument _GW2Maps = new XmlDocument();
        
        private static GW2DataSource _GW2DataSource = new GW2DataSource();
                
        private ObservableCollection<GW2ServerGroup> _allGroups = new ObservableCollection<GW2ServerGroup>();
        public ObservableCollection<GW2ServerGroup> AllGroups
        {
            get { return this._allGroups; }
        }

        public static IEnumerable<GW2ServerGroup> GetGroups(string uniqueId)
        {
            if (!uniqueId.Equals("AllGroups")) throw new ArgumentException("Only 'AllGroups' is supported as a collection of groups");
            
            return _GW2DataSource.AllGroups;
        }

        public static GW2ServerGroup GetGroup(string uniqueId)
        {
            // Simple linear search is acceptable for small data sets
            var matches = _GW2DataSource.AllGroups.Where((group) => group.UniqueId.Equals(uniqueId));
            if (matches.Count() == 1) return matches.First();
            return null;
        }

        public static GW2ServerItem GetItem(string uniqueId)
        {
            // Simple linear search is acceptable for small data sets
            var matches = _GW2DataSource.AllGroups.SelectMany(group => group.Items).Where((item) => item.UniqueId.Equals(uniqueId));
            if (matches.Count() == 1) return matches.First();
            return null;
        }

        #region NrOfServers
        public static int NrOfServers()
        {
            return _GW2Worlds.ChildNodes[1].ChildNodes.Count();
        }
        #endregion

        #region NrOfEvents
        public static int NrOfEvents()
        {
            return _GW2Events.ChildNodes[1].ChildNodes.Count();
        }
        #endregion

        #region NrOfMaps
        public static int NrOfMaps()
        {
            return _GW2Maps.ChildNodes[1].ChildNodes.Count();
        }
        #endregion

        #region ReadServersFromDisk
        public static void ReadServersFromDisk()
        {
            int counter = 0;

            XmlElement root = _GW2Worlds.DocumentElement;
            XmlNodeList nodes = root.SelectNodes("world");
            foreach (var item in nodes)
            {
                counter++;

                String serverGroupId = item.ChildNodes[0].InnerText.Substring(0, 2);
                if (_GW2DataSource.AllGroups.FirstOrDefault(c => c.UniqueId.Equals(serverGroupId)) == null)
                {
                    // This server group does not exist yet,
                    // so it must be created and added to the list of server groups
                    switch (item.ChildNodes[0].InnerText.Substring(0, 2))
                    {
                        case "10":
                            GW2ServerGroup GW2America = new GW2ServerGroup("10", "American servers", "/Assets/DarkGray.png");
                            _GW2DataSource.AllGroups.Add(GW2America);
                            break;
                        case "20":
                            GW2ServerGroup GW2Europe = new GW2ServerGroup("20", "European servers", "/Assets/DarkGray.png");
                            _GW2DataSource.AllGroups.Add(GW2Europe);
                            break;
                        case "21":
                            GW2ServerGroup GW2French = new GW2ServerGroup("21", "French servers", "/Assets/DarkGray.png");
                            _GW2DataSource.AllGroups.Add(GW2French);
                            break;
                        case "22":
                            GW2ServerGroup GW2German = new GW2ServerGroup("22", "German servers", "/Assets/DarkGray.png");
                            _GW2DataSource.AllGroups.Add(GW2German);
                            break;
                        case "23":
                            GW2ServerGroup GW2Spanish = new GW2ServerGroup("23", "Spanish servers", "/Assets/DarkGray.png");
                            _GW2DataSource.AllGroups.Add(GW2Spanish);
                            break;
                        default:
                            GW2ServerGroup GW2Other = new GW2ServerGroup(serverGroupId, "Unknown servers", "/Assets/DarkGray.png");
                            _GW2DataSource.AllGroups.Add(GW2Other);
                            break;
                    }
                }

                GW2ServerItem GW2Server = new GW2ServerItem();

                GW2Server.Group = _GW2DataSource.AllGroups.FirstOrDefault(c => c.UniqueId.Equals(serverGroupId));
                GW2Server.SetImage("/Assets/" + counter.ToString() + ".jpg");
                GW2Server.Title = item.ChildNodes[1].InnerText;
                GW2Server.UniqueId = item.ChildNodes[0].InnerText;

                if (GW2Server.Group.Items.FirstOrDefault(m => m.UniqueId.Equals(GW2Server.UniqueId)) == null)
                {
                    // Ths server does not exist yet

                    // All fields have been processed
                    // Select the group and add this item to the items in this group
                    _GW2DataSource.AllGroups.FirstOrDefault(c => c.UniqueId.Equals(serverGroupId)).Items.Add(GW2Server);
                }
            }
        }
        #endregion

        #region LoadDataAsync
        /// <summary>
        /// Load data asynchronously from the app data files into appropriate XML documents
        /// </summary>
        /// <returns></returns>
        public static async Task LoadDataAsync()
        {
            // Setup Worlds Data File
            await SetupDataFile(Constants.XML_FILE_DIR + "\\" + Constants.WORLDS + Constants.XML_FILE_NAME, Constants.WORLDS + Constants.XML_FILE_NAME);

            // Read Worlds from XML File into XML Document structure
            _GW2Worlds = await Utility.ReadXMLDocumentFromFileAsync(Constants.WORLDS + Constants.XML_FILE_NAME);

            // Setup Events Data File
            await SetupDataFile(Constants.XML_FILE_DIR + "\\" + Constants.EVENTS + Constants.XML_FILE_NAME, Constants.EVENTS + Constants.XML_FILE_NAME);

            // Read Events from XML File into XML Document structure
            _GW2Events = await Utility.ReadXMLDocumentFromFileAsync(Constants.EVENTS + Constants.XML_FILE_NAME);

            // Setup Maps Data File
            await SetupDataFile(Constants.XML_FILE_DIR + "\\" + Constants.MAPS + Constants.XML_FILE_NAME, Constants.MAPS + Constants.XML_FILE_NAME);

            // Read Maps from XML File into XML Document structure
            _GW2Maps = await Utility.ReadXMLDocumentFromFileAsync(Constants.MAPS + Constants.XML_FILE_NAME);
        }
        #endregion

        #region Handle file manipulation

        #region DeleteFile
        public static async Task<bool> DeleteFile(string dstFileName)
        {
            try
            {
                // Determine data folder location
                StorageFolder dataFolder = ApplicationData.Current.LocalFolder;
                bool completedXMLfileExists = await CheckFile(dstFileName, dataFolder);

                if (completedXMLfileExists)
                {
                    // Copy master file to data folder
                    StorageFile srcFile = await dataFolder.GetFileAsync(dstFileName);
                    await srcFile.DeleteAsync();
                }
                return true;
            }
            catch
            {
                return false;
            }
        }
        #endregion

        #region CheckFile
        public static async Task<bool> CheckFile(string filePath, StorageFolder fileFolder)
        {
            try
            {
                StorageFile file = await fileFolder.GetFileAsync(filePath);
                return true;
            }
            catch (System.IO.FileNotFoundException)
            {
                return false;
            }
        }
        #endregion

        #region SetupDataFile
        /// <summary>
        /// SetupDataFile ensures that the installation directory contains the required file
        /// If the file is not present, the pre-delivered base file is copied into the correct location 
        /// </summary>
        /// <param name="srcFileName">Source File Name</param>
        /// <param name="dstFileName">Destination File Name</param>
        /// <returns></returns>
        public static async Task SetupDataFile(string srcFileName, string dstFileName)
        {
            try
            {
                // Determine application folder location
                StorageFolder installedLocation = Package.Current.InstalledLocation;
                bool masterXMLfileExists = await CheckFile(srcFileName, installedLocation);

                // Determine data folder location
                StorageFolder dataFolder = ApplicationData.Current.LocalFolder;
                bool completedXMLfileExists = await CheckFile(dstFileName, dataFolder);

                if (!completedXMLfileExists && masterXMLfileExists)
                {
                    // Copy master file to data folder
                    StorageFile srcFile = await installedLocation.GetFileAsync(srcFileName);
                    await srcFile.CopyAsync(dataFolder);
                }
            }
            catch
            {
            }
        }
        #endregion

        #endregion

    }
}
